﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using log4net;

namespace ExcelAutomator
{
	public interface IParameterPatternParser
	{
		TConfiguration Parse<TConfiguration>(TConfiguration configuration, ExecutionContext context);
		TConfiguration Parse<TConfiguration>(TConfiguration configuration, ExecutionContext context, IEnumerable<Type> patternResolverFilter);
	}

	[Export(typeof(IParameterPatternParser))]
	internal class ParameterPatternParser : IParameterPatternParser
	{
		public ParameterPatternParser()
		{
			
		}

		[ImportMany(typeof(IParameterPatternResolver))]
		private IEnumerable<IParameterPatternResolver> m_PatternResolvers;

		public IDictionary<string, IParameterPatternResolver> PatternResolversById
		{
			get
			{
				return m_PatternResolvers
					.DistinctBy(x=>x.PatternId)
					.ToDictionary(x=>x.PatternId);
			}
			
		}

		#region Parse

		public TConfiguration Parse<TConfiguration>(TConfiguration configuration, ExecutionContext context, IEnumerable<Type> patternResolverFilter)
		{
			var result = new Dictionary<string, IParameterPatternResolver>();
			foreach (var type in patternResolverFilter)
			{
				foreach (IParameterPatternResolver resolver in PatternResolversById.Values)
				{
					if (!type.IsAssignableFrom(resolver.GetType()))
					{
						result.Add(resolver.PatternId, resolver);
					}
				}
			}
			return Parse(configuration, context, result);
		}

		public TConfiguration Parse<TConfiguration>(TConfiguration configuration, ExecutionContext context)
		{
			return Parse(configuration, context, PatternResolversById);
		}

		private TConfiguration Parse<TConfiguration>(TConfiguration configuration, ExecutionContext context, IDictionary<string, IParameterPatternResolver> resolvers)
		{
			foreach (PropertyInfo propertyInfo in configuration.GetType()
				.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty))
			{
				var value = propertyInfo.GetValue(configuration, null);

				if (value != null)
				{
					foreach (var key in ExtractParameterIds(value.ToString()))
					{
						if (resolvers.ContainsKey(key))
						{
							var resolvedValue = resolvers[key].Resolve(value.ToString(), propertyInfo.Name, context);
							var parsedValue = ParseValue(value.ToString(), resolvedValue, key);
							UpdateConfigurationObject(propertyInfo, configuration, parsedValue);
						}
					}
				}
			}
			return configuration;
		}

		#endregion

		#region ParseValue

		private static string ParseValue(string value, string resolvedValue, string key)
		{
			if (value.Equals(resolvedValue))
			{
				return value;
			}
			return value.Replace(string.Format("{0}", key), resolvedValue);
		}

		private static void UpdateConfigurationObject(PropertyInfo propertyInfo, object configuration, string parsedValue)
		{
			propertyInfo.SetValue(configuration, parsedValue, null);
		}

		#endregion

		#region ExtractParameterIds

		private const string m_ExtractResolverIdRegExPattern = @"%[\p{L}]+";

		private static IEnumerable<string> ExtractParameterIds(string fullParameterValue)
		{
			if(Regex.IsMatch(fullParameterValue, m_ExtractResolverIdRegExPattern))
			{
				foreach (Match match in Regex.Matches(fullParameterValue, m_ExtractResolverIdRegExPattern))
				{
					yield return match.Value;
				}
			}
		}

		#endregion

		#region Log

		private static ILog Log
		{
			get { return LogManager.GetLogger(typeof (ParameterPatternParser)); }
		}

		#endregion

		
	}
}
